/*
 * HTGeodesic.java
 * www.bouthier.net
 *
 * The MIT License :
 * -----------------
 * Copyright (c) 2001 Christophe Bouthier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

package hypertree.model;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.QuadCurve2D;

/**
 * The HTGeodesic class implements a geodesic linking to points in the Poincarre
 * model.
 * 
 * @author Christophe Bouthier [bouthier@loria.fr]
 * @version 1.0
 */
public class HTGeodesic
{

	private static final double	EPSILON		= 1.0E-10;					// epsilon

	private static final int	LINE		= 0;						// draw
	// a
	// line
	private static final int	ARC			= 1;						// draw
	// an
	// arc

	private int					type		= HTGeodesic.LINE;			// type
	// of
	// the
	// geodesic

	private HTCoordE			za			= null;					// first
	// point
	// (Euclidian)
	private HTCoordE			zb			= null;					// second
	// point
	// (Euclidian)
	private HTCoordE			zc			= null;					// control
	// point
	// (Euclidian)
	private HTCoordE			zo			= null;					// center of
	// the
	// geodesic;

	private HTCoordS			a			= null;					// first
	// point (on
	// the
	// screen)
	private HTCoordS			b			= null;					// second
	// point (on
	// the
	// screen)
	private HTCoordS			c			= null;					// control
	// point (on
	// the
	// screen)

	private int					d			= 0;

	private boolean				kleinMode	= false;

	private final QuadCurve2D	curve		= new QuadCurve2D.Double();

	/* --- Constructor --- */

	/**
	 * Constructor.
	 * 
	 * @param za
	 *            the first point
	 * @param zb
	 *            the second point
	 */
	public HTGeodesic(HTCoordE za, HTCoordE zb)
	{
		this.za = za;
		this.zb = zb;

		this.zc = new HTCoordE();
		this.zo = new HTCoordE();

		this.a = new HTCoordS();
		this.b = new HTCoordS();
		this.c = new HTCoordS();

		this.rebuild();
	}

	/* --- Refresh --- */

	/**
	 * Refresh the screen coordinates of this node.
	 * 
	 * @param sOrigin
	 *            the origin of the screen plane
	 * @param sMax
	 *            the (xMax, yMax) point in the screen plane
	 */
	public void refreshScreenCoordinates(HTCoordS sOrigin, HTCoordS sMax)
	{
		if (this.kleinMode)
		{
			this.a.projectionEtoS(this.za.pToK(), sOrigin, sMax);
			this.b.projectionEtoS(this.zb.pToK(), sOrigin, sMax);
		}
		else
		{
			this.a.projectionEtoS(this.za, sOrigin, sMax);
			this.b.projectionEtoS(this.zb, sOrigin, sMax);
			this.c.projectionEtoS(this.zc, sOrigin, sMax);

			this.d = ((this.a.x - this.b.x) * (this.a.x - this.b.x))
					+ ((this.a.y - this.b.y) * (this.a.y - this.b.y));
			if (this.d != 0)
			{
				this.curve.setCurve(this.a.x, this.a.y, this.c.x, this.c.y,
						this.b.x, this.b.y);
			}
		}
	}

	/* --- Rebuild --- */

	/**
	 * Builds the geodesic.
	 */
	public void rebuild()
	{
		if (!this.kleinMode)
		{
			if (// za == origin
			(Math.abs(this.za.d()) < HTGeodesic.EPSILON) || // zb == origin
					(Math.abs(this.zb.d()) < HTGeodesic.EPSILON) || // za =
					// lambda.zb
					(Math
							.abs((this.za.x / this.zb.x)
									- (this.za.y / this.zb.y)) < HTGeodesic.EPSILON))
			{
				this.type = HTGeodesic.LINE;
			}
			else
			{
				this.type = HTGeodesic.ARC;

				double da = 1 + this.za.x * this.za.x + this.za.y * this.za.y;
				double db = 1 + this.zb.x * this.zb.x + this.zb.y * this.zb.y;
				double dd = 2 * (this.za.x * this.zb.y - this.zb.x * this.za.y);

				this.zo.x = (this.zb.y * da - this.za.y * db) / dd;
				this.zo.y = (this.za.x * db - this.zb.x * da) / dd;

				double det = (this.zb.x - this.zo.x) * (this.za.y - this.zo.y)
						- (this.za.x - this.zo.x) * (this.zb.y - this.zo.y);
				double fa = this.za.y * (this.za.y - this.zo.y) - this.za.x
						* (this.zo.x - this.za.x);
				double fb = this.zb.y * (this.zb.y - this.zo.y) - this.zb.x
						* (this.zo.x - this.zb.x);

				this.zc.x = ((this.za.y - this.zo.y) * fb - (this.zb.y - this.zo.y)
						* fa)
						/ det;
				this.zc.y = ((this.zo.x - this.za.x) * fb - (this.zo.x - this.zb.x)
						* fa)
						/ det;
			}
		}
	}

	/* --- Draw --- */

	/**
	 * Draws this geodesic.
	 * 
	 * @param g
	 *            the graphic context
	 */
	public void draw(Graphics2D g)
	{
		g.setColor(Color.black);
		if (g instanceof Graphics2D)
		{
			if (this.kleinMode)
			{
				g.drawLine(this.a.x, this.a.y, this.b.x, this.b.y);
			}
			else
			{
				switch (this.type)
				{
					case LINE:
						g.drawLine(this.a.x, this.a.y, this.b.x, this.b.y);
						break;
					case ARC:
						if (this.d != 0)
						{
							g.draw(this.curve);
						}
						break;
					default:
						break;
				}
			}
		}
		else
		{
			System.err.println("Error : Hypertree 1.0 requires Java 1.2 "
					+ "or superior.");
		}
	}

	/* --- Klein --- */

	/**
	 * Sets the klein mode.
	 * 
	 * @param mode
	 *            setting on or off
	 */
	public void kleinMode(boolean mode)
	{
		if (mode != this.kleinMode)
		{
			this.kleinMode = mode;
		}
	}

	/* --- ToString --- */

	/**
	 * Returns a string representation of the object.
	 * 
	 * @return a String representation of the object
	 */
	@Override
	public String toString()
	{
		String result = "Geodesic betweens : " + "\n\t A: " + this.za
				+ "\n\t B: " + this.zb + "\n\t is ";
		switch (this.type)
		{
			case LINE:
				result += "a line.";
				break;
			case ARC:
				result += "an arc.";
				break;
			default:
				result += "nothing ?";
				break;
		}
		return result;
	}

}
